{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "635d8ebb",
   "metadata": {
    "id": "635d8ebb"
   },
   "source": [
    "# Creating Runnable objects with chain decorator\n",
    "\n",
    "- Author: [Yejin Park](https://github.com/ppakyeah)\n",
    "- Peer Review: []()\n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/07-ChainDecorator.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/07-ChainDecorator.ipynb)\n",
    "## Overview\n",
    "\n",
    "This tutorial explains how to convert regular functions into Runnable objects using the ```@chain``` decorator.\n",
    "\n",
    "We'll cover ```ChatPromptTemplate``` for prompt creation, function transformation with ```@chain```.\n",
    "\n",
    "The practical exercise demonstrates how to builde a custom chain that converts text into Instagram-style posts with emojis.\n",
    "\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environement Setup](#environment-setup)\n",
    "- [Creating Runnable objects: RunnableLambda vs chain decorator](#creating-runnable-objects-runnablelambda-vs-chain-decorator)\n",
    "- [Using the RunnableLambda](#using-the-runnablelambda)\n",
    "- [Using the chain decorator](#using-the-chain-decorator)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangChain: Runnable interface](https://python.langchain.com/docs/concepts/runnables/)\n",
    "- [LangChain: LangChain Expression Language (LCEL)](https://python.langchain.com/docs/concepts/lcel/)\n",
    "- [LangChain: The convenience @chain decorator](https://python.langchain.com/docs/how_to/functions/#the-convenience-chain-decorator)\n",
    "- [RunnableLambda API Reference](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.RunnableLambda.html#runnablelambda)\n",
    "- [Chain API Referece](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.chain.html)\n",
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6c7aba4",
   "metadata": {
    "id": "c6c7aba4"
   },
   "source": [
    "## Environment Setup\n",
    "\n",
    "Setting up your environment is the first step. See the [Environment Setup](https://wikidocs.net/257836) guide for more details.\n",
    "\n",
    "**[Note]**\n",
    "- The ```langchain-opentutorial``` is a bundle of easy-to-use environment setup guidance, useful functions and utilities for tutorials.\n",
    "- Check out the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "21943adb",
   "metadata": {
    "id": "21943adb"
   },
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "f25ec196",
   "metadata": {
    "id": "f25ec196"
   },
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langchain_core\",\n",
    "        \"langchain_openai\",\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5d4e865",
   "metadata": {},
   "source": [
    "You can set API keys in a .env file or set them manually.\n",
    "\n",
    "**[Note]** If you’re not using the .env file, no worries! Just enter the keys directly in the cell below, and you’re good to go."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "7f9065ea",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "7f9065ea",
    "outputId": "fc49e364-0174-4869-c9f9-c87f6438ff04"
   },
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "# Attempt to load environment variables from a .env file; if unsuccessful, set them manually.\n",
    "if not load_dotenv():\n",
    "    set_env(\n",
    "        {\n",
    "            \"OPENAI_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_API_KEY\": \"\",\n",
    "            \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "            \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "            \"LANGCHAIN_PROJECT\": \"07-ChainDecorator\",\n",
    "        }\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa00c3f4",
   "metadata": {
    "id": "aa00c3f4"
   },
   "source": [
    "## Creating Runnable objects: RunnableLambda vs. chain decorator\n",
    "\n",
    "As highlighted in LangChain's [Conceptual guide](https://python.langchain.com/docs/concepts/runnables/#overview-of-runnable-interface), the Runnable interface is a core concept going with many LangChain components. When we use LangChain Expression Language (LCEL) to create a Runnable, we often call it a **chain**. This means that **chains** are a specific type of Runnable and therefore inherit all the properties and methods of a Runnable object.\n",
    "\n",
    "You can create these objects from regular Python functions using two primary methods: ```RunnableLambda``` and the ```@chain``` decorator.\n",
    "\n",
    "Let's see how it works in practice!\n",
    "\n",
    "Define two prompt templates using the ```ChatPromptTemplate``` class:\n",
    "\n",
    "- ```prompt1``` requests a brief description of a given topic.\n",
    "- ```prompt2``` requests the creation of an Instagram post using emojis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "c449ceb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import chain\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# Define prompt templates\n",
    "prompt1 = ChatPromptTemplate.from_template(\"Please provide a brief description in English about {topic}.\")\n",
    "prompt2 = ChatPromptTemplate.from_template(\n",
    "    \"Please create an Instagram post using emojis for the following text: {sentence}\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4f3b3fe",
   "metadata": {},
   "source": [
    "### Using the RunnableLambda\n",
    "Let's check the following example, wrapping a regular function ```instagram_post_generator()``` with ```RunnableLambda``` to create a Runnable object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c6b33ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🌌✨ Dive into the fascinating world of quantum mechanics! 🔬💫 \n",
      "\n",
      "🧬 This fundamental branch of physics explores the behavior of matter and energy at the tiniest scales—think atoms and subatomic particles! ⚛️💥\n",
      "\n",
      "📏 Unlike classical mechanics, quantum mechanics introduces mind-bending concepts like:\n",
      "🌊🌀 Wave-particle duality (particles can be both wave-like and particle-like!),\n",
      "🔄 Superposition (particles can exist in multiple states at once!),\n",
      "🔗 Entanglement (where particles are interconnected across distances! 😲)\n",
      "\n",
      "🔍 These principles reshape our understanding of reality and fuel groundbreaking technologies such as:\n",
      "💻 Quantum computing,\n",
      "💡 Lasers,\n",
      "🔌 Semiconductors!\n",
      "\n",
      "Join us on this journey to unravel the mysteries of the quantum realm! 🌟🔭 #QuantumMechanics #Physics #ScienceIsCool #WaveParticleDuality #QuantumComputing #Entanglement #Superposition #Technology #NatureOfReality\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.runnables import RunnableLambda\n",
    "\n",
    "# Using RunnableLambda\n",
    "def instagram_post_generator(text):\n",
    "    chain1 = prompt1 | ChatOpenAI(model=\"gpt-4o-mini\") | StrOutputParser()\n",
    "    output1 = chain1.invoke({\"topic\": text})\n",
    "    chain2 = prompt2 | ChatOpenAI(model=\"gpt-4o-mini\") | StrOutputParser()\n",
    "    return chain2.invoke({\"sentence\": output1})\n",
    "\n",
    "runnable_chain = RunnableLambda(instagram_post_generator)\n",
    "print(runnable_chain.invoke(\"quantum mechanics\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be70c389",
   "metadata": {},
   "source": [
    "### Using the chain decorator\n",
    "You can convert any function into a chain by adding the ```@chain``` decorator.\n",
    "\n",
    "This does the same thing as wrapping the function in ```RunnableLambda```. However, the ```@chain``` decorator provides a cleaner and more maintainable way to create Runnable objects in your LangChain applications."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd3108b3",
   "metadata": {
    "id": "fd3108b3"
   },
   "source": [
    "The ```custom_chain``` function executes a custom chain based on the input text.\n",
    "- By decorating this function with ```@chain```, we convert it into a Runnable object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "4ebcd91c",
   "metadata": {
    "id": "4ebcd91c"
   },
   "outputs": [],
   "source": [
    "@chain\n",
    "def custom_chain(text):\n",
    "    # Create a chain by connecting the first prompt, ChatOpenAI, and string output parser\n",
    "    chain1 = prompt1 | ChatOpenAI(model=\"gpt-4o-mini\") | StrOutputParser()\n",
    "    output1 = chain1.invoke({\"topic\": text})\n",
    "\n",
    "    # Create a chain by connecting the second prompt, ChatOpenAI, and string output parser\n",
    "    chain2 = prompt2 | ChatOpenAI(model=\"gpt-4o-mini\") | StrOutputParser()\n",
    "    # Call the second chain with the parsed first result and return the final result\n",
    "    return chain2.invoke({\"sentence\": output1})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa4f79aa",
   "metadata": {
    "id": "aa4f79aa"
   },
   "source": [
    "Since ```custom_chain``` is now a Runnable object, it must be executed using ```invoke()```."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "beef019e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "beef019e",
    "outputId": "7326f0f4-6881-4883-e964-4257a45973d3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🌌✨ Dive into the mysterious world of #QuantumMechanics! 🔬💫 \n",
      "\n",
      "This fundamental branch of physics reveals how matter and energy behave at the tiniest scales, like atoms and subatomic particles. 🧬⚛️ \n",
      "\n",
      "🌀 **Wave-Particle Duality**: Electrons can be both waves and particles! 🌊➡️⚛️ \n",
      "\n",
      "🔄 **Superposition**: Systems can exist in multiple states at once! 🎭✨ \n",
      "\n",
      "🔗 **Entanglement**: Particles can be connected in a way that the state of one affects the other, no matter the distance! 🌌❤️ \n",
      "\n",
      "These mind-blowing concepts are shaping our understanding of the universe and powering technologies like semiconductors, lasers, and quantum computing! 💻🔋 \n",
      "\n",
      "#Physics #Science #Universe #Technology #Innovation #Quantum #ExploreTheUnknown 🚀🔍🧪\n"
     ]
    }
   ],
   "source": [
    "# Call custom_chain\n",
    "print(custom_chain.invoke(\"quantum mechanics\"))"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": [],
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
